Creating a modern web application involves combining a robust backend with an intuitive and dynamic frontend. In this guide, we’ll walk you through building a fully functional To-Do app that seamlessly integrates Django REST Framework (DRF) for the backend and React.js for the frontend. DRF provides a powerful, flexible API layer, while React enhances the user experience with interactive and responsive design. By the end of this project, you’ll have a solid understanding of how to connect these two technologies to create a practical, real-world application that is both efficient and visually appealing. Whether you're new to full-stack development or looking to refine your skills, this step-by-step approach will set you up for success.
Creating a Django REST API for a To-Do List project is a great way to practice working with Django and the Django REST Framework (DRF). Below is a step-by-step guide to building the project:
Backend Develop
1. Setup the Environment
Install Django and Django REST Framework
pip install django djangorestframework
Start a Django Project
django-admin startproject todo_project cd todo_project
Create a New App
python manage.py startapp todo
Add the app and DRF to INSTALLED_APPS in todo_project/settings.py:
INSTALLED_APPS = [ ... 'rest_framework', 'todo', ]
2. Define the Model
In todo/models.py, define a model for your To-Do items:
from django.db import models class Todo(models.Model): title = models.CharField(max_length=255) description = models.TextField(blank=True, null=True) is_completed = models.BooleanField(default=False) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return self.title
Run migrations:
python manage.py makemigrations python manage.py migrate
3. Create a Serializer
In todo/serializers.py:
from rest_framework import serializers from .models import Todo class TodoSerializer(serializers.ModelSerializer): class Meta: model = Todo fields = '__all__'
4. Build the Views
In todo/views.py:
from rest_framework import viewsets from .models import Todo from .serializers import TodoSerializer class TodoViewSet(viewsets.ModelViewSet): queryset = Todo.objects.all() serializer_class = TodoSerializer
5. Configure the URLs
In todo/urls.py:
from django.urls import path, include from rest_framework.routers import DefaultRouter from .views import TodoViewSet router = DefaultRouter() router.register(r'todos', TodoViewSet) urlpatterns = [ path('', include(router.urls)), ]
Include the app's URLs in the project urls.py:
from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('api/', include('todo.urls')), # API endpoints ]
6. Test the API
Run the server:
python manage.py runserver
Visit http://127.0.0.1:8000/api/todos/ in your browser or use a tool like Postman to interact with the API.
Endpoints
7. Enhancements (Optional)
If you prefer function-based views (FBVs) instead of class-based views, here's how you can build the To-Do List API.
1. Update ViewsReplace the viewsets approach with function-based views using Django REST Framework's @api_view decorator.
from rest_framework.decorators import api_view from rest_framework.response import Response from rest_framework import status from .models import Todo from .serializers import TodoSerializer @api_view(['GET', 'POST']) def todo_list(request): """ Handle GET for listing all to-dos and POST for creating a new to-do. """ if request.method == 'GET': todos = Todo.objects.all() serializer = TodoSerializer(todos, many=True) return Response(serializer.data) elif request.method == 'POST': serializer = TodoSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @api_view(['GET', 'PUT', 'DELETE']) def todo_detail(request, pk): """ Handle GET, PUT, and DELETE for a specific to-do item. """ try: todo = Todo.objects.get(pk=pk) except Todo.DoesNotExist: return Response({'error': 'Todo not found'}, status=status.HTTP_404_NOT_FOUND) if request.method == 'GET': serializer = TodoSerializer(todo) return Response(serializer.data) elif request.method == 'PUT': serializer = TodoSerializer(todo, data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) elif request.method == 'DELETE': todo.delete() return Response({'message': 'Todo deleted successfully'}, status=status.HTTP_204_NO_CONTENT)
2. Update URLsIn todo/urls.py:
from django.urls import path from . import views urlpatterns = [ path('todos/', views.todo_list, name='todo-list'), path('todos/<int:pk>/', views.todo_detail, name='todo-detail'), ]
Include these URLs in your main urls.py:
from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('api/', include('todo.urls')), # Include To-Do app API URLs ]
3. Testing the API
Test the endpoints using a tool like Postman, cURL, or a browser for GET requests.
4. Example API CallsCreate a To-Do (POST /api/todos/)Request Body (JSON):
{ "title": "Learn Django", "description": "Build a To-Do app using Django REST Framework", "is_completed": false }
Update a To-Do (PUT /api/todos/1/)Request Body (JSON):
{ "title": "Learn Django REST", "description": "Build an app with FBVs", "is_completed": true }
This approach provides a simple and lightweight way to implement a REST API using function-based views.
Frontend Develop
Integrating your Django REST API with a React.js frontend is a great idea! Below is a step-by-step guide to building a React frontend for your To-Do list API.
1. Setup the React ProjectInstall React
npx create-react-app todo-frontend cd todo-frontend
Install AxiosAxios is a library for making HTTP requests, which we'll use to interact with the API.
npm install axios
2. Create the To-Do Components Directory StructureOrganize your files as follows:
src/ ├── components/ │ ├── TodoList.js │ ├── TodoForm.js ├── App.js ├── index.js
TodoList Component The TodoList component will fetch and display the list of To-Do items from the API.
import React, { useState, useEffect } from 'react'; import axios from 'axios'; const TodoList = ({ onEdit }) => { const [todos, setTodos] = useState([]); // Fetch todos on component mount useEffect(() => { axios .get('http://127.0.0.1:8000/api/todos/') .then((response) => setTodos(response.data)) .catch((error) => console.error('Error fetching todos:', error)); }, []); const handleDelete = (id) => { axios .delete(`http://127.0.0.1:8000/api/todos/${id}/`) .then(() => { setTodos(todos.filter((todo) => todo.id !== id)); }) .catch((error) => console.error('Error deleting todo:', error)); }; return ( <div> <h2>To-Do List</h2> <ul> {todos.map((todo) => ( <li key={todo.id}> <strong>{todo.title}</strong> - {todo.description} <button onClick={() => onEdit(todo)}>Edit</button> <button onClick={() => handleDelete(todo.id)}>Delete</button> </li> ))} </ul> </div> ); }; export default TodoList;
TodoForm Component The TodoForm component will handle adding and editing To-Do items.
import React, { useState } from 'react'; import axios from 'axios'; const TodoForm = ({ currentTodo, clearEdit }) => { const [title, setTitle] = useState(currentTodo?.title || ''); const [description, setDescription] = useState(currentTodo?.description || ''); const [isCompleted, setIsCompleted] = useState(currentTodo?.is_completed || false); const handleSubmit = (event) => { event.preventDefault(); const todoData = { title, description, is_completed: isCompleted }; if (currentTodo) { // Update an existing To-Do axios .put(`http://127.0.0.1:8000/api/todos/${currentTodo.id}/`, todoData) .then(() => { clearEdit(); }) .catch((error) => console.error('Error updating todo:', error)); } else { // Create a new To-Do axios .post('http://127.0.0.1:8000/api/todos/', todoData) .then(() => { setTitle(''); setDescription(''); setIsCompleted(false); }) .catch((error) => console.error('Error creating todo:', error)); } }; return ( <form onSubmit={handleSubmit}> <h2>{currentTodo ? 'Edit To-Do' : 'Add New To-Do'}</h2> <input type="text" placeholder="Title" value={title} onChange={(e) => setTitle(e.target.value)} required /> <textarea placeholder="Description" value={description} onChange={(e) => setDescription(e.target.value)} /> <label> <input type="checkbox" checked={isCompleted} onChange={(e) => setIsCompleted(e.target.checked)} /> Completed </label> <button type="submit">{currentTodo ? 'Update' : 'Add'}</button> {currentTodo && <button onClick={clearEdit}>Cancel</button>} </form> ); }; export default TodoForm;
App Component Combine the TodoList and TodoForm components in the main App.js.
import React, { useState } from 'react'; import TodoList from './components/TodoList'; import TodoForm from './components/TodoForm'; const App = () => { const [currentTodo, setCurrentTodo] = useState(null); const handleEdit = (todo) => { setCurrentTodo(todo); }; const clearEdit = () => { setCurrentTodo(null); }; return ( <div> <h1>To-Do App</h1> <TodoForm currentTodo={currentTodo} clearEdit={clearEdit} /> <TodoList onEdit={handleEdit} /> </div> ); }; export default App;
3. Run the React App Start the React development server:
npm start
4. Connecting Django and React Enable Cross-Origin RequestsInstall and configure Django CORS Headers to allow the React app to access the API.
pip install django-cors-headers
Add corsheaders to INSTALLED_APPS in settings.py:
INSTALLED_APPS = [ ... 'corsheaders', ]
Add CorsMiddleware to MIDDLEWARE:
MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', ... ]
Allow all origins (for development purposes) by adding this to settings.py:
CORS_ALLOW_ALL_ORIGINS = True
With this setup, your React frontend should now be fully integrated with your Django REST API!
Thank you for your reading.